package com.yst.sqlbuilder;


import com.yst.lambda.TableFieldFunc;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public abstract class SQLJoin<L,R> implements ISQLExpression, IParentedSelect<L>, IAliasExpression {
    private final SQLSelect<L> parentSelect;
    protected SQLJoin(SQLSelect<L> parentSelect){
        this.parentSelect=parentSelect;
    }
    private Object joinClass;
    public SQLJoin<L,R> join(Class<R> data){
        this.joinClass=data;
        return this;
    }

    abstract String getJoinKeyword();
    public SQLJoin<L,R> join(IAliasExpression data){
        this.joinClass=data;
        return this;
    }
    protected SQLExpGroup<L,R> expGroup;
    public SQLJoin<L,R> on(SQLExpGroup<L,R> expGroup){
        this.expGroup=expGroup;
        return this;
    };
    public SQLJoin<L,R> on(SQLExp<L,R> exp){
        this.expGroup=new SQLExpGroup<>(IExpressionGroup.LinkMode.AND,exp);
        return this;
    }
    public SQLJoin<L,R> on(TableFieldFunc<L,?> fieldLeft,TableFieldFunc<R,?> fieldRight,Symbol symbol){
        if (StringUtils.isNotEmpty(getAlias())) {
            this.expGroup = new SQLExpGroup<>(IExpressionGroup.LinkMode.AND,
                    new SQLExp<>(new SQLFieldExp<>(parentSelect,fieldLeft),
                            new SQLFieldExp<>(this,fieldRight), symbol));
        }
        else {
            this.expGroup = new SQLExpGroup<>(IExpressionGroup.LinkMode.AND,
                    new SQLExp<>(new SQLFieldExp<>(fieldLeft,parentSelect),
                            new SQLFieldExp<>(this,fieldRight), symbol));
        }
        return this;
    }
    public <V> SQLJoin<L,R> onEqual(TableFieldFunc<L,V> fieldLeft,TableFieldFunc<R,V> fieldRight){
        return on(fieldLeft,fieldRight,Symbol.EQ);
    }
    @Override
    public SQLSelect<L> getParentSelect() {
        return parentSelect;
    }
    @Override
    public String generateExpression(GenerateParam param) {
        assert joinClass!=null;
        String result=getJoinKeyword();
        if (joinClass instanceof ISQLSelect) {
            ISQLSelect select=(ISQLSelect) joinClass;
            if (StringUtils.isNotEmpty(getAlias())) {
                result= result.concat(" (\r\n")
                        .concat("  ").concat(select.generateExpression(param))
                        .concat(StringUtils.leftPad("", getLevel() * 2, " "))
                        .concat(") AS ").concat(getAlias());
            }
            else {
                throw new RuntimeException("Select symbol used for join,must be set alias!");
            }
        } else if (joinClass instanceof Class){
            String tableName;
            tableName= getEntitiesConfig().getTableName((Class<?>) joinClass);
            if (StringUtils.isEmpty(getAlias())) {
                result= result.concat(" ").concat(tableName);
            }
            else {
                result = result.concat(" ").concat(tableName).concat(" AS ").concat(getAlias());
            }
        }
        else {
            result= result.concat(" ").concat(getEntitiesConfig().getTableName(joinClass.getClass()));
        }
        if (Objects.isNull(expGroup)) {
            log.warn("you not set condition's expression for join! the data will to be so much");
            return result;
        }
        return result.concat(" ON ").concat(expGroup.generateExpression(param));
    }
    private final List<SQLField<R>> selectFieldList=new ArrayList<>();
    public List<SQLField<R>> getFieldList(){
        return Collections.unmodifiableList(selectFieldList);
    }
    @SafeVarargs
    public final SQLJoin<L,R> select(TableFieldFunc<R, ?>... fields){
        selectFieldList.addAll(Arrays.stream(fields)
                .map((it)->new SQLField<>(it).setTableAlias(this)).collect(Collectors.toList()));
        return this;
    }
    public final SQLJoin<L,R> select(TableFieldFunc<R,?> field,String fieldAlias){
        selectFieldList.add(new SQLField<>(field).setAlias(fieldAlias).setTableAlias(this));
        return this;
    }

    @SafeVarargs
    public final SQLJoin<L,R> select(SQLField<R>...fields){
        for(SQLField<R> field:fields){
            field.setTableAlias(this);
        }
        selectFieldList.addAll(Arrays.asList(fields));
        return this;
    }
    private String alias;
    @Override
    public String getAlias() {
        return alias;
    }
    public SQLJoin<L,R> as(String alias){
        this.alias=alias;
        return this;
    }
    @Getter
    private final SQLJoinCondition<L,R> where=new SQLJoinCondition<>(this);
}
